/*
 * NewLeaf.h
 *
 *  Created on: Oct 28, 2012
 *      Author: Federico Orquera
 */

#ifndef NEWLEAF_H_
#define NEWLEAF_H_

#include "NewNode.h"

template <
	typename keyTemplate,
	typename valueTemplate,
	bool keyComparator(
		const keyTemplate& key1,
		const keyTemplate& key2
	)
>
class NewLeaf: public NewNode<keyTemplate, valueTemplate, keyComparator> {

public:

	typedef NewNode<keyTemplate, valueTemplate, keyComparator> Node;

	typedef typename Node::Key_t 					Key_t;
	typedef typename Node::Value_t 					Value_t;
	typedef typename Node::NodeOrder 				NodeOrder;
	typedef typename Node::NodeLevel 				NodeLevel;
	typedef typename Node::NodeID 					NodeID;
	typedef typename Node::registerType 			registerType;
	typedef typename Node::register_ptr 			register_ptr;
	typedef typename Node::registerPointerList 		registerPointerList;
	typedef typename Node::searchResult				searchResult;
	typedef typename Node::SimpleRegisterList		SimpleRegisterList;
	typedef typename Node::registerPointerList_ptr	registerPointerList_ptr;

	struct SerializationBlock{
	    NodeLevel               level;
	    NodeID                  rightSibling;
	    registerPointerList_ptr content;
	};

	NewLeaf(const NodeOrder& nodeOrder, const NodeID& newNodeID):
		Node(nodeOrder,0,newNodeID){
		initialize();
	}

	NewLeaf(
			const NodeOrder& nodeOrder,
			const NodeID& newNodeID,
			bool (*isOverflow) (const registerPointerList& list),
			bool (*isUnderflow)(const registerPointerList& list)
	): Node( nodeOrder,0,newNodeID ){
		initialize( NULL_NODE_ID, isOverflow, isUnderflow );
	}

	NewLeaf(
            const NodeOrder& nodeOrder,
            const NodeID& newNodeID,
            size_t (*getSize) (const NewLeaf* node)
    ): Node( nodeOrder,0,newNodeID ){
        initialize( NULL_NODE_ID, NULL, NULL, getSize );
    }

	NewLeaf(const NodeOrder& nodeOrder, const NodeID& newNodeID, const NodeID& newSibling):
		Node(nodeOrder,0,newNodeID){
		initialize(newSibling);
	}

	NewLeaf(
			const NodeOrder& nodeOrder,
			const NodeID& newNodeID,
			const NodeID& newSibling,
			const registerPointerList_ptr& newContent,
			bool (*isOverflow) (const registerPointerList& list),
			bool (*isUnderflow)(const registerPointerList& list)
	): Node(nodeOrder,0,newNodeID){
		initialize( newSibling, isOverflow, isUnderflow );
		this->setContent(newContent);
	}

	NewLeaf(
			const NodeOrder& nodeOrder,
			const NodeID& newNodeID,
			const NodeID& newSibling,
			const registerPointerList_ptr& newContent,
			size_t (*getSize) (const NewLeaf* node)
	): Node(nodeOrder,0,newNodeID){
		initialize( newSibling, NULL, NULL, getSize );
		this->setContent(newContent);
	}

	NewLeaf(
			const NodeOrder& nodeOrder,
			const NodeID& newNodeID,
			const NodeID& newSibling,
			bool (*isOverflow) (const registerPointerList& list),
			bool (*isUnderflow)(const registerPointerList& list)
	): Node(nodeOrder,0,newNodeID){
		initialize( newSibling, isOverflow, isUnderflow );
	}

	~NewLeaf(){}

	const size_t getSize(){
		if( getSizeFunction != NULL ) return getSizeFunction(this);
		else return pRegisterList.getList().size();
	}

	const NodeID& getRightSibling() const {
		return this->rightSibling;
	}

	void setRightSibling(NodeID newrightSibling) {
		if(newrightSibling == this->getNodeID())
	    	throw NodeExceptions("Right Sibling can't be equal to NodeID.");
		this->rightSibling = newrightSibling;
	}

	registerPointerList_ptr& getContent(){
		return this->pRegisterList.getContent();
	}

	void setContent(const registerPointerList& newContent) {
		this->pRegisterList.getList() = newContent;
	}

	const register_ptr& first(){
		return this->pRegisterList.getList().front();
	}
	const register_ptr& last(){
		return this->pRegisterList.getList().back();
	}

	SerializationBlock getSerializationBlock() const{
	    SerializationBlock returnBlock;
	    returnBlock.level = this->getLevel();
	    returnBlock.rightSibling = this->getRightSibling();
	    returnBlock.content = ((NewLeaf*)this)->getContent();

	    return returnBlock;
	}

	/*
	 * Deja la lista de los primeros resgistros hasta estar en Underflow +1 en el nodo, y devuelve la otra "mitad"
	 */
	registerPointerList getHalfList(){

		registerPointerList returnList, bufferList;

		while(!this->isUnderflow()){
			bufferList.push_back(pRegisterList.getList().front());
			pRegisterList.getList().pop_front();
		}

		returnList = pRegisterList.getList();
		pRegisterList.getList() = bufferList;

		return returnList;

	}

	NodeResulMessages LoadRegister( const register_ptr& registro ){
		if(this->isOverflow())
			return NODE_OVERFLOW;
		this->pRegisterList.getList().push_back(registro);
		return UPDATED;
	}

	NodeResulMessages InsertRegister( const register_ptr& registro ){

		if( !this->isEmpty() && this->registerExists(registro) )
	    	throw NodeExceptions("Trying to insert a register with duplicate key.");

		this->pRegisterList.simpleInsertRegister(registro);

		if(this->isOverflow())
			return NODE_OVERFLOW;

	    return UPDATED;
	}

	NodeResulMessages UpdateRegister( const register_ptr& registro ){

	    if( !this->isEmpty() && !this->registerExists(registro) )
	    	throw NodeExceptions("Trying to modify a register that doesn't exist.");
	    this->pRegisterList.simpleDeleteRegister(registro->getKey());
	    this->pRegisterList.simpleInsertRegister(registro);
	    if(this->isOverflow())
			return NODE_OVERFLOW;
		return UPDATED;
	}

	NodeResulMessages DeleteRegister( const Key_t& key ){
		this->pRegisterList.simpleDeleteRegister(key);
		if(this->isUnderflow())
			return UNDERFLOW;
		return UPDATED;
	}

	searchResult begin( ){
		searchResult returnValue;

		returnValue.result 		= this->pRegisterList.first();
		returnValue.nextNode	= this->getNodeID();
		returnValue.valid 		= true;
		returnValue.endOfList 	= false;

		return returnValue;
	}

	searchResult SearchRegister( const Key_t& key ){

		searchResult returnValue;

		if(this->isEmpty()){
			returnValue.nextNode	= NULL_NODE_ID;
			returnValue.valid 		= false;
			returnValue.endOfList 	= true;
			return returnValue;
		}

		this->pRegisterList.simpleInternalSearch(key);
		if(this->pRegisterList.getIterator() != this->pRegisterList.getList().end()){
			returnValue.result 		= (*this->pRegisterList.getIterator());
			returnValue.nextNode	= this->getNodeID();
			returnValue.valid 		= ((*this->pRegisterList.getIterator())->getKey() == key);
			returnValue.endOfList 	= false;
		}
		else{ // it == end()
			auto elem = *(--this->pRegisterList.getIterator());
			returnValue.result 		= elem;
			returnValue.nextNode	= this->getRightSibling();
			returnValue.valid 		= ( elem->getKey() == key );
			returnValue.endOfList 	= true;
		}

		return returnValue;

	}

	searchResult SearchNextRegister( const Key_t& key ){

		searchResult returnValue;
		typename registerPointerList::iterator it;

		it = this->pRegisterList.simpleInternalSearch(key);

		if(!keyComparator( key, (*it)->getKey() ))
			it++;

		if( it != this->pRegisterList.getList().end()){
			returnValue.result 	  = (*it);
			returnValue.valid 	  = ((*it)->getKey() != key);

			it++;

			if(it != this->pRegisterList.getList().end()){
				returnValue.nextNode  = this->getNodeID();
				returnValue.endOfList = false;
			}else{
				returnValue.nextNode  =  this->getRightSibling();
				returnValue.endOfList = (this->getRightSibling() == NULL_NODE_ID);
			}
		}

		return returnValue;

	}

	void MostrarContenidoReducido(const string& separator = "") {

		// ID: LVL | SIZE | (KEY_1)(KEY_N)RIGHT_SIBLING

		cout<<separator<<this->n<<": "<<this->l<<" | "<<this->getSize()<<" | ";

		for ( this->it=pRegisterList.getList().begin() ; this->it != pRegisterList.getList().end(); this->it++ )
			cout <<"("<< (*this->it)->getKey() << ")";

		cout<<this->getRightSibling();
		cout<<endl;

	}

	void MostrarContenido(const string& separator = "") {

		cout<<separator<<"NodeOrder: "	<< this->m;
		cout<<endl;
		cout<<separator<<"NodeLevel: "	<< this->l;
		cout<<endl;
		cout<<separator<<"NodeID: "		<< this->n;
		cout<<endl;
		cout<<separator<<"Right Sibling: "<< this->getRightSibling();
		cout<<endl;
		cout<<separator<<"Size: "		<< pRegisterList.getList().size();
		cout<<endl;
		if(pRegisterList.getList().size()>0)cout<<separator<<"Contents: "	<< endl;

		for ( this->it=pRegisterList.getList().begin() ; this->it != pRegisterList.getList().end(); this->it++ ){
			cout << separator << "\t key: "   << (*this->it)->getKey();
			cout << endl << endl;
		}

	}

private:

	// Persisitr
	NodeID 				rightSibling;
	SimpleRegisterList 	pRegisterList;

	// No Perisistir
	typename 			registerPointerList::iterator  		it;
	bool   (*isOverflowFunction) (const registerPointerList& list);
	bool   (*isUnderflowFunction)(const registerPointerList& list);
	size_t (*getSizeFunction) 	 (const NewLeaf* node);

	bool isEmpty(){
	    return this->pRegisterList.getList().empty();
	}

	bool registerExists( const register_ptr& registro ){
	               //return ( this->SearchRegister(registro->getKey()).result->getKey() == registro->getKey() );
	               return this->SearchRegister(registro->getKey()).valid;
	}

	bool isOverflow( const float& factor = 1.0 ){
        if( isOverflowFunction != NULL ) return isOverflowFunction(pRegisterList.getList());
        if( getSizeFunction != NULL ) return ( getSizeFunction(this) >= factor*this->m );
        return ( pRegisterList.getList().size() > this->m );
    }

    bool isUnderflow( const float& factor = 0.5 ){
        if( isUnderflowFunction != NULL ) return isUnderflowFunction(pRegisterList.getList());
        if( getSizeFunction != NULL ) return ( getSizeFunction(this) <= factor*this->m );
        return ( pRegisterList.getList().size() < this->m/2 );
    }

	void setContent(const registerPointerList_ptr& newContent) {
		this->pRegisterList.setContent(newContent);
	}

	void initialize(
			const NodeID& newSibling = NULL_NODE_ID,
			bool (*isOverflow) (const registerPointerList& list) = NULL,
			bool (*isUnderflow)(const registerPointerList& list) = NULL,
			size_t (*getSize) (const NewLeaf* node) = NULL
	){
		this->setRightSibling(newSibling);
		this->isOverflowFunction  = isOverflow;
		this->isUnderflowFunction = isUnderflow;
		this->getSizeFunction = getSize;
	}

};


#endif /* NEWLEAF_H_ */
